﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;

namespace Eneta.Portal.Forum.Rss
{
    [Serializable]
    public class RssFeed
    {
        // Fields
        private bool cached;
        private RssChannelCollection channels;
        private Encoding encoding;
        private string etag;
        private ExceptionCollection exceptions;
        private DateTime lastModified;
        private RssModuleCollection modules;
        private RssVersion rssVersion;
        private string url;

        // Methods
        public RssFeed()
        {
            this.channels = new RssChannelCollection();
            this.modules = new RssModuleCollection();
            this.exceptions = null;
            this.lastModified = RssDefault.DateTime;
            this.rssVersion = RssVersion.Empty;
            this.cached = false;
            this.etag = "";
            this.url = "";
            this.encoding = null;
        }

        public RssFeed(Encoding encoding)
        {
            this.channels = new RssChannelCollection();
            this.modules = new RssModuleCollection();
            this.exceptions = null;
            this.lastModified = RssDefault.DateTime;
            this.rssVersion = RssVersion.Empty;
            this.cached = false;
            this.etag = "";
            this.url = "";
            this.encoding = null;
            this.encoding = encoding;
        }

        private static RssFeed read(string url, HttpWebRequest request, RssFeed oldFeed)
        {
            RssFeed feed = new RssFeed();
            RssElement element = null;
            Stream responseStream = null;
            Uri uri = new Uri(url);
            feed.url = url;
            switch (uri.Scheme)
            {
                case "file":
                    feed.lastModified = File.GetLastWriteTime(url);
                    if ((oldFeed != null) && (feed.LastModified == oldFeed.LastModified))
                    {
                        oldFeed.cached = true;
                        return oldFeed;
                    }
                    responseStream = new FileStream(url, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                    break;

                case "https":
                case "http":
                    if (request == null)
                    {
                        request = (HttpWebRequest) WebRequest.Create(uri);
                    }
                    if (oldFeed != null)
                    {
                        request.IfModifiedSince = oldFeed.LastModified;
                        request.Headers.Add("If-None-Match", oldFeed.ETag);
                    }
                    try
                    {
                        HttpWebResponse response = (HttpWebResponse) request.GetResponse();
                        feed.lastModified = response.LastModified;
                        feed.etag = response.Headers["ETag"];
                        try
                        {
                            if (response.ContentEncoding != "")
                            {
                                feed.encoding = Encoding.GetEncoding(response.ContentEncoding);
                            }
                        }
                        catch
                        {
                        }
                        responseStream = response.GetResponseStream();
                    }
                    catch (WebException exception)
                    {
                        if (oldFeed == null)
                        {
                            throw exception;
                        }
                        oldFeed.cached = true;
                        return oldFeed;
                    }
                    break;
            }
            if (responseStream != null)
            {
                RssReader reader = null;
                try
                {
                    reader = new RssReader(responseStream);
                    do
                    {
                        element = reader.Read();
                        if (element is RssChannel)
                        {
                            feed.Channels.Add((RssChannel)element);
                        }
                    }
                    while (element != null);
                    feed.rssVersion = reader.Version;
                    return feed;
                }
                finally
                {
                    feed.exceptions = reader.Exceptions;
                    reader.Close();
                }
            }
            throw new ApplicationException("Not a valid Url");
        }

        public static RssFeed Read(RssFeed oldFeed)
        {
            return read(oldFeed.url, null, oldFeed);
        }

        public static RssFeed Read(HttpWebRequest Request)
        {
            return read(Request.RequestUri.ToString(), Request, null);
        }

        public static RssFeed Read(string url)
        {
            return read(url, null, null);
        }

        public static RssFeed Read(HttpWebRequest Request, RssFeed oldFeed)
        {
            return read(oldFeed.url, Request, oldFeed);
        }

        public override string ToString()
        {
            return this.url;
        }

        private void write(RssWriter writer)
        {
            try
            {
                if (this.channels.Count == 0)
                {
                    throw new InvalidOperationException("Feed must contain at least one channel.");
                }
                writer.Version = this.rssVersion;
                writer.Modules = this.modules;
                foreach (RssChannel channel in this.channels)
                {
                    if (channel.Items.Count == 0)
                    {
                        throw new InvalidOperationException("Channel must contain at least one item.");
                    }
                    writer.Write(channel);
                }
            }
            finally
            {
                if (writer != null)
                {
                    writer.Close();
                }
            }
        }

        public void Write(Stream stream)
        {
            RssWriter writer;
            if (this.encoding == null)
            {
                writer = new RssWriter(stream);
            }
            else
            {
                writer = new RssWriter(stream, this.encoding);
            }
            this.write(writer);
        }

        public void Write(string fileName)
        {
            RssWriter writer = new RssWriter(fileName);
            this.write(writer);
        }

        // Properties
        public bool Cached
        {
            get
            {
                return this.cached;
            }
        }

        public RssChannelCollection Channels
        {
            get
            {
                return this.channels;
            }
        }

        public Encoding Encoding
        {
            get
            {
                return this.encoding;
            }
            set
            {
                this.encoding = value;
            }
        }

        public string ETag
        {
            get
            {
                return this.etag;
            }
        }

        public ExceptionCollection Exceptions
        {
            get
            {
                if (this.exceptions != null)
                {
                    return this.exceptions;
                }
                return new ExceptionCollection();
            }
        }

        public DateTime LastModified
        {
            get
            {
                return this.lastModified;
            }
        }

        public RssModuleCollection Modules
        {
            get
            {
                return this.modules;
            }
        }

        public string Url
        {
            get
            {
                return this.url;
            }
        }

        public RssVersion Version
        {
            get
            {
                return this.rssVersion;
            }
            set
            {
                this.rssVersion = value;
            }
        }
    }
}
